// PonPoff.cpp: implementation of the CPonPoff class.
//
//////////////////////////////////////////////////////////////////////

#include "ASample.h"
#include "PonPoff.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CPonPoff::CPonPoff(int nDim)
{
	strcpy(m_strID, "PonPoff");
	m_nDim = nDim;
	m_fT = 1;

	m_p_binindex = NULL;
}

CPonPoff::CPonPoff(vfeaid &vfid)
{
	m_vFeaID = vfid;
	m_nDim = vfid.size();
	strcpy(m_strID, "PonPoff");
	m_fT = 1;

	m_p_binindex = NULL;
}

CPonPoff::~CPonPoff()
{
	if (m_p_binindex != NULL)
		delete []m_p_binindex;
	m_p_binindex = NULL;
}

float CPonPoff::Classify(CASample *pSample, const char flag)
{
	static vfloat vfFeature;
	int size=pSample->GetFeatures(vfFeature, m_vFeaID);

	return Classify(vfFeature, flag);
}

bool CPonPoff::Classify(vfloat &vfFeature, float fT, const char flag)
{
	SetThreshold(fT);
	return Classify(vfFeature, flag);
}

void CPonPoff::CreateIndexTable(void)
{
	// free allocated space
	if (m_p_binindex != NULL)
		delete []m_p_binindex;
	m_p_binindex = NULL;
	// no data
	if (m_nDim <= 0)
		return;

	int		offset=0,k,j;
	float	fvalue,delta_bin,delta_temp;

	m_p_binindex = new vint[m_nDim];
	m_min_values.resize(m_nDim);
	m_max_values.resize(m_nDim);
	m_delta.resize(m_nDim);
	for (int i=0; i<m_nDim; i++)
	{
		m_min_values[i] = m_vfBinBoundaries[offset];
		m_max_values[i] = m_vfBinBoundaries[offset+m_vnBins[i]-1];
		m_vfBinBoundaries[offset+m_vnBins[i]-2];
		//ASSERT(m_min_values[i] < m_max_values[i]); Jiadi Yang

		// find the minimal delta among all the bins
		delta_bin = 1e8;
		for (j=0; j<m_vnBins[i]-1; j++)
		{
			delta_temp = m_vfBinBoundaries[offset+j+1]-m_vfBinBoundaries[offset+j];
			if (delta_temp < delta_bin)
				delta_bin = delta_temp;
		}

		m_delta[i] = (float)max(delta_bin / 10.0, (m_max_values[i] - m_min_values[i]) / 1000.0);
		m_p_binindex[i].resize((int)((m_max_values[i]-m_min_values[i])/m_delta[i])+1);

		fvalue = m_min_values[i];
		j = 0;
		for (k=0; k<m_p_binindex[i].size(); k++)
		{
			float aa = m_vfBinBoundaries[offset+j];
			if (fvalue > m_vfBinBoundaries[offset+j])
				j++;
			m_p_binindex[i][k] = j;
			fvalue += m_delta[i];
		}

		offset += m_vnBins[i];
	}
}


bool CPonPoff::Classify(vfloat &vfFeature, const char flag)
{
	//ASSERT(vfFeature.size() == m_nDim); // Jiadi Yang
	int offset = 0;
	int i,j,idx = 0;
	
	if (flag == DETECT_TEXT)
	{
		for (i = 0; i < m_nDim; i++)
		{
			// doesn't matter for the last element, it is supposed to be +infinity
	/*		float a1,a2,f1,f2;
			for (j = 0; j < m_vnBins[i] - 1; j++)
			{
				if (vfFeature[i] <= m_vfBinBoundaries[offset + j])
					break;
			}
			k = GetIndex(vfFeature[i], i);
			if (k != j)
				k = k;

			f1 = m_min_values[i];
			f2 = m_max_values[i];
			a1 = vfFeature[i];
			a2 = m_vfBinBoundaries[offset + j];
	*/
			j = GetIndex(vfFeature[i], i);

	//		vnIdx.push_back(j);
			idx = j + idx * m_vnBins[i];

			offset += m_vnBins[i];
		}
	}
	else
	{
		for ( i = 0; i < m_nDim; i++)
		{
			// doesn't matter for the last element, it is supposed to be +infinity
			for ( j = 0; j < m_vnBins[i] - 1; j++)
			{
				if (vfFeature[i] <= m_vfBinBoundaries[offset + j])
					break;
			}
			idx = j + idx * m_vnBins[i];

			offset += m_vnBins[i];
		}
	
	}
	//	return m_vbBins[idx].fLLR > m_fT;
	return m_vbBins[idx].fPon > m_fT * m_vbBins[idx].fPoff;
}

void CPonPoff::FillBins(vfloat &vfPos, vfloat &vfNeg)
{
	m_vbBins.resize(0);
	int nBins = 1;
	int i, j, k; 
	for ( i = 0; i < m_nDim; i ++)
	{
		nBins *= m_vnBins[i];
	}
	m_vbBins.resize(nBins);

	int nPos = vfPos.size();
	//ASSERT(nPos % m_nDim == 0); Jiadi Yang
	nPos /= m_nDim;

	// fill Pon's
	for (i = 0; i < nPos; i ++)
	{
		int offset = 0;
		int idx = 0;
		for (j = 0; j < m_nDim; j++)
		{
			// doesn't matter for the last element, it is supposed to be +infinity
			for (k = 0; k < m_vnBins[j] - 1; k++)
			{
				if (vfPos[j*nPos + i] <= m_vfBinBoundaries[offset + k])
					break;
			}
			idx = k + idx * m_vnBins[j];
			offset += m_vnBins[j];
		}
		m_vbBins[idx].fPon ++;
	}
	// normalize Pon's
	for (i = 0; i < nBins; i++)
		m_vbBins[i].fPon /= nPos;

	int nNeg = vfNeg.size();
	//ASSERT(nNeg % m_nDim == 0); Jiadi Yang
	nNeg /= m_nDim;

	// fill Poff's
	for (i = 0; i < nNeg; i ++)
	{
		int offset = 0;
		int idx = 0;
		for (int j = 0; j < m_nDim; j++)
		{
			// doesn't matter for the last element, it is supposed to be +infinity
			for (k = 0; k < m_vnBins[j] - 1; k++)
			{
				if (vfNeg[j*nNeg + i] <= m_vfBinBoundaries[offset + k])
					break;
			}
			idx = k + idx * m_vnBins[j];
			offset += m_vnBins[j];
		}
		m_vbBins[idx].fPoff ++;
	}
	// normalize Poff's
	nBins = m_vbBins.size();
	for (i = 0; i < nBins; i++)
		m_vbBins[i].fPoff /= nNeg;
}

float CPonPoff::SumSqrtPonPoff()
{
	float fret = 0;

	int nBins = m_vbBins.size();
	for (int i = 0; i < nBins; i ++)
	{
		fret += sqrt(m_vbBins[i].fPon * m_vbBins[i].fPoff);
	}
	
	return fret;
}

//
//	vfPos: Positive samples, each sample has m_nDim features
//	vfNeg: Negative samples, each sample has m_nDim features
//      features are stored as first feature for all samples, then 2nd...
//

void CPonPoff::BuildBins(vfloat &vfPos, vfloat &vfNeg)
{
	int nPos = vfPos.size();
	//ASSERT(nPos % m_nDim == 0); Jiadi Yang
	nPos /= m_nDim;

	int nNeg = vfNeg.size();
//	ASSERT(nNeg % m_nDim == 0); Jiadi YAng
	nNeg /= m_nDim;

	// get min and max value for each dimension of all the samples
	// para for fdelta
	float fbinnum = 100;
	float fscale = fbinnum / (fbinnum - 1);

	vfloat vfmin, vfmax;
	int idxPos = 0, idxNeg = 0;
	int i, j;
	for (i = 0; i < m_nDim; i++)
	{
		float fmin, fmax;
		fmin = fmax = vfPos[idxPos];
		for (j = 1; j < nPos; j++)
		{
			if (fmin > vfPos[idxPos + j])
				fmin = vfPos[idxPos + j];
			if (fmax < vfPos[idxPos + j])
				fmax = vfPos[idxPos + j];
		}
		idxPos += nPos;
		
		fmax *= fscale;


/*		for (j = 0; j < nNeg; j++)
		{
			if (fmin > vfNeg[idxNeg + j])
				fmin = vfNeg[idxNeg + j];
			if (fmax < vfNeg[idxNeg + j])
				fmax = vfNeg[idxNeg + j];
		}
		idxNeg += nNeg;
*/
		vfmin.push_back(fmin);
		vfmax.push_back(fmax);
	}

	// init bins: 1 bin for 1 dim
	m_vnBins.resize(0);
	m_vfBinBoundaries.resize(0);
	for (i = 0; i < m_nDim; i++)
	{
		m_vnBins.push_back(1);
		m_vfBinBoundaries.push_back(vfmax[i]);
	}
	m_vbBins.resize(1);

	// build the bins
	float fmin, fbmin, fold, fnew=1;
	CPonPoff ppold, ppmin;
	int nBins, dimmin, it = 0;
	
	do{
		it ++;
//		printf("%3d..", it);
		fold = fnew;
		ppold = *this;
		// for each dimension and each possible bin boundaries, try to find the mininum divide
		fmin = 1;
		int idx = 0;
		for (i = 0; i < m_nDim; i++)
		{
			float fdelta = (vfmax[i] - vfmin[i])/fbinnum;
			for (float fb = vfmin[i]; fb < vfmax[i]; fb += fdelta)
			{
				// locate the candidate in the existing boundaries
				for (j = 0; j < m_vnBins[i]; j++)
				{
					if ((fb < m_vfBinBoundaries[idx + j]) ||
						(fb == m_vfBinBoundaries[idx + j]) )
						break;
				}
				// an existing boundary
				if (fb == m_vfBinBoundaries[idx + j])
					continue;

				// Add into the array
				m_vfBinBoundaries.insert(m_vfBinBoundaries.begin()+idx+j, fb); //&m_vfBinBoundaries[idx + j]
				m_vnBins[i]++;
				FillBins(vfPos, vfNeg);
				fnew = SumSqrtPonPoff();
				if (fnew < fmin)
				{
					fmin = fnew;
					fbmin = fb;
					dimmin = i;
					ppmin = *this;
				}
				*this = ppold;
			}
			idx += m_vnBins[i];
		}
		*this = ppmin;
		fnew = fmin;
		
//		printf("f=%4.2f,df=%5.3f,dim=%d,b=%6.2f ", fmin, fold-fnew,dimmin, fbmin);

		// performance on training set
		float fPon, fPoff;
		fPon = fPoff = 0;
		nBins = m_vbBins.size();
		for (i = 0; i < nBins; i++)
		{
			if (m_vbBins[i].fPon >= m_vbBins[i].fPoff)
				fPon += m_vbBins[i].fPon;
			else
				fPoff += m_vbBins[i].fPoff;
		}
//		printf("nbin=%04d,Pon=%5.3f,Poff=%5.3f\n", nBins, fPon, fPoff);
	}while(fold - fnew > 0.001 && it < m_nDim*10 && nBins < nPos/5);

//	WriteFile("/TextImages/bins.dat");
//	ReadFile("/TextImages/bins.dat");
	// write down the results
//	printf("\nBuilt Bins\n");
	int idx = 0;
	for (i = 0; i < m_nDim; i++)
	{
//		printf("Dim%02d nbin = %02d => ", i, m_vnBins[i]);
		for (int j = 0; j < m_vnBins[i]; j++)
//			printf("%06.2f; ", m_vfBinBoundaries[idx + j] );
//		printf("\n");
		idx += m_vnBins[i];
	}

	// performance on training set
	float fPon, fPoff;
	fPon = fPoff = 0;
	nBins = m_vbBins.size();
	for (i = 0; i < nBins; i++)
	{
		if (m_vbBins[i].fPon >= m_vbBins[i].fPoff)
			fPon += m_vbBins[i].fPon;
		else
			fPoff += m_vbBins[i].fPoff;
	}
//	printf("Pon = %6.4f, Poff = %6.4f\n", fPon, fPoff);
}


bool CPonPoff::LoadFromFile(const char *strFileName)
{
	FILE *fp = fopen(strFileName, "rb");
	if (fp == NULL)
		return false;

	// clear all the stuff
	m_vnBins.resize(0);
	m_vfBinBoundaries.resize(0);
	m_vbBins.resize(0);

	LoadFromFile(fp);

	fclose(fp);

	return true;
}

void CPonPoff::LoadFromFile(FILE *fp)
{
	// Read classifier ID
	fread(&m_strID, sizeof(char), 20, fp);
	// read IDs of features used in the classifier
	int nFeaID;
	fread(&nFeaID, sizeof(int), 1, fp);
	m_vFeaID.resize(nFeaID);
	int i;
	for (int i = 0; i < nFeaID; i ++)
	{
		CFeaID &fid = m_vFeaID[i];
		fread(fid.strFeaName, sizeof(char), 2, fp);
		fread(&fid.nFeaIdx, sizeof(int), 1, fp);
	}

	// write number of dimension
	fread(&m_nDim, sizeof(int), 1, fp);

	// write nmber of bins for each dimension
	m_vnBins.resize(m_nDim);
	int nbb=0, nb=1;
	for (i = 0; i < m_nDim; i ++)
	{
		fread(&m_vnBins[i], sizeof(int), 1, fp);
		nbb += m_vnBins[i];
		nb  *= m_vnBins[i];
	}

	// write bin boundaries
	m_vfBinBoundaries.resize(nbb);
	for (i = 0; i < m_vfBinBoundaries.size(); i++)
	{
		fread(&m_vfBinBoundaries[i], sizeof(float), 1, fp);
	}

	// write all the bins
	m_vbBins.resize(nb);
	for (i = 0; i < m_vbBins.size(); i++)
	{
		CBin &bin = m_vbBins[i];
		fread(&bin.fPon,  sizeof(float), 1, fp);
		fread(&bin.fPoff, sizeof(float), 1, fp);
	}

	// index for the bins
	CreateIndexTable();
}


bool CPonPoff::SaveToFile(const char *strFileName)
{
	FILE *fp = fopen(strFileName, "wb");
	if (fp == NULL)
		return false;

	SaveToFile(fp);

	fclose(fp);

	return true;
}

void CPonPoff::SaveToFile(FILE *fp)
{
	// Write classifier ID
	fwrite(&m_strID, sizeof(char), 20, fp);
	// Write IDs of features used in the classifier
	int nFeaID = m_vFeaID.size();
	fwrite(&nFeaID, sizeof(int), 1, fp);
	int i;
	for (int i = 0; i < nFeaID; i ++)
	{
		CFeaID &fid = m_vFeaID[i];
		fwrite(fid.strFeaName, sizeof(char), 2, fp);
		fwrite(&fid.nFeaIdx, sizeof(int), 1, fp);
	}

	// write number of dimension
	fwrite(&m_nDim, sizeof(int), 1, fp);
	// write nmber of bins for each dimension
	for (i = 0; i < m_nDim; i ++)
	{
		fwrite(&m_vnBins[i], sizeof(int), 1, fp);
	}
	// write bin boundaries
	for (i = 0; i < m_vfBinBoundaries.size(); i++)
	{
		fwrite(&m_vfBinBoundaries[i], sizeof(float), 1, fp);
	}
	// write all the bins
	for (i = 0; i < m_vbBins.size(); i++)
	{
		CBin &bin = m_vbBins[i];
		fwrite(&bin.fPon,  sizeof(float), 1, fp);
		fwrite(&bin.fPoff, sizeof(float), 1, fp);
	}
}


void CPonPoff::Clear()
{

}
